﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static Marmat.Forms.Skin.SkinScrollControl;

namespace Marmat.Forms.Skin
{
    /// <summary>
    /// 滚动条的消息处理。
    /// </summary>
    public class ScrollableControlExtension
    {
        /// <summary>
        /// 构建滚动条的消息处理实例。
        /// </summary>
        /// <param name="control"></param>
        public ScrollableControlExtension(ScrollableControl control)
        {
            this.Control = control;
            this.SetScrollSize(6);
        }

        /// <summary>
        /// 需要处理的滚动条控件。
        /// </summary>
        internal ScrollableControl Control { get; }

        /// <summary>
        /// 获取或设置用户控件的边框样式。
        /// </summary>
        /// <returns>System.Windows.Forms.BorderStyle 值之一。 默认值为 System.Windows.Forms.BorderStyle.None。</returns>
        public BorderStyle BorderStyle { get; set; } = BorderStyle.None;

        /// <summary>
        /// 获取或设置控件的边框颜色
        /// </summary>
        public Color BorderColor { get; set; } = Color.Gainsboro;

        /// <summary>
        /// 获取控件的客户区矩形
        /// </summary>
        internal protected Rectangle ClipRectangle
        {
            get
            {
                if (this.Control is System.Windows.Forms.Form form)
                    return form.ClientRectangle;
                else
                    return new Rectangle(0, 0, this.Control.Width, this.Control.Height);
            }
        }

        /// <summary>
        /// 是否开启虚拟滚动条
        /// </summary>
        readonly bool VirtualScroll = false;

        /// <summary>
        /// 垂直滚动条的矩形位置。
        /// </summary>
        private Rectangle vertical_scroll_rect;
        /// <summary>
        /// 水平滚动条的矩形位置。
        /// </summary>
        private Rectangle horizontal_scroll_rect;
        /// <summary>
        /// 垂直滚动条的焦点状态。
        /// </summary>
        private bool vertical_scroll_foucs = false;
        /// <summary>
        /// 水平滚动条的焦点状态。
        /// </summary>
        private bool horizontal_scroll_foucs = false;
        /// <summary>
        /// 垂直滚动条的矩形区域。
        /// </summary>
        private Rectangle vertical_scroll_Rectangle;
        /// <summary>
        /// 水平滚动条的矩形区域。
        /// </summary>
        private Rectangle horizontal_scroll_Rectangle;
        /// <summary>
        /// 是否正在拖拽垂直滚动条。
        /// </summary>
        private protected bool drag_vertical_scroll = false;
        /// <summary>
        /// 是否正在拖拽水平滚动条。
        /// </summary>
        private protected bool drag_horizontal_scroll = false;
        /// <summary>
        /// 准备拖拽时滚动条的值。
        /// </summary>
        private int drag_scroll_value;
        /// <summary>
        /// 准备拖拽时鼠标的坐标。
        /// </summary>
        private Point drag_scroll_location;
        /// <summary>
        /// 滚动边框的大小
        /// </summary>
        protected Size scrollBorderSize = new Size(11, 11);
        /// <summary>
        /// 滚动条的大小。
        /// </summary>
        protected Size scrollSize = new(6, 6);

        /// <summary>
        /// 滚动条默认状态下的画刷
        /// </summary>
        public Brush DefaultBrush { get; set; } = new SolidBrush(Color.FromArgb(205, 205, 205));

        /// <summary>
        /// 滚动条处于焦点下的画刷
        /// </summary>
        public Brush HoverBrush { get; set; } = new SolidBrush(Color.FromArgb(166, 166, 166));

        /// <summary>
        /// 绘制滚动条背景图时发生。
        /// </summary>
        public event EventHandler<PaintEventArgs> PaintScrollBackgroundImage;

        /// <summary>
        /// 设置滚动区域的边框大小。
        /// </summary>
        /// <param name="width">边框的宽度。</param>
        public void SetScrollBorder(int width)
        {
            this.scrollBorderSize = new Size(width, width);
        }

        /// <summary>
        /// 设置滚动条的大小。
        /// </summary>
        /// <param name="width">滚动条的宽度/高度。</param>
        public void SetScrollSize(int width)
        {
            this.scrollSize = new Size(width, width);
        }

        /// <summary>
        /// 处理 Windows 消息之前执行,用于修改 Windows 消息内容。
        /// </summary>
        /// <param name="m"></param>
        /// <param name="arguments"></param>
        /// <returns>如果返回true,则需要用户执行 WndProc 函数</returns>
        public bool BeforeWndProc(ref Message m, out object arguments)
        {
            arguments = default;
            bool result = true;
            Point location = this.Control.PointToClient(System.Windows.Forms.Control.MousePosition);
            bool visibleScroll = this.Control.VerticalScroll.Maximum > this.ClipRectangle.Height || this.Control.AutoScrollMinSize.Width > this.ClipRectangle.Width; //获取是否需要显示滚动条

            switch (m.Msg)
            {
                case Win32API.Messages.WM_NCHITTEST:
                    // 如果滚动条之前的状态为焦点,但是鼠标已经移走,则刷新滚动条的UI。(鼠标从非客户区移入客户区,会导致滚动条接收不到消息)
                    if (this.vertical_scroll_foucs && !vertical_scroll_rect.Contains(location))
                    {
                        this.InvalidateScroll(m);
                        result = false;
                    }
                    // 如果滚动条之前的状态为焦点,但是鼠标已经移走,则刷新滚动条的UI。(鼠标从非客户区移入客户区,会导致滚动条接收不到消息)
                    else if (this.horizontal_scroll_foucs && !horizontal_scroll_rect.Contains(location))
                    {
                        this.InvalidateScroll(m);
                        result = false;
                    }
                    // 由于拦截了 WM_NCHITTEST 消息,导致在非客户区按下鼠标左键并拖动,导致无法接收事件,通过设置返回值(19),由控件自行处理事件。
                    else if (vertical_scroll_Rectangle.Contains(location))
                    {
                        m.Result = new IntPtr(19);
                        result = false;
                    }
                    // 由于拦截了 WM_NCHITTEST 消息,导致在非客户区按下鼠标左键并拖动,导致无法接收事件,通过设置返回值(19),由控件自行处理事件。
                    else if (horizontal_scroll_Rectangle.Contains(location))
                    {
                        m.Result = new IntPtr(19);
                        result = false;
                    }
                    break;
                case Win32API.Messages.WM_NCCALCSIZE:
                    // 处理非客户区
                    if (this.Control is System.Windows.Forms.Form == true)
                        break;
                    RECT rect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
                    int left = 0, top = 0, right = 0, bottom = 0;
                    if (this.Control.VerticalScroll.Visible)
                        right += this.scrollBorderSize.Width;
                    if (this.Control.HorizontalScroll.Visible)
                        bottom += this.scrollBorderSize.Height;
                    if (this.BorderStyle != BorderStyle.None)
                    {
                        left++; top++; right++; bottom++;
                    }
                    rect.Left += left;
                    rect.Top += top;
                    rect.Right -= right;
                    rect.Bottom -= bottom;
                    Marshal.StructureToPtr(rect, m.LParam, true); // 将修改完毕的区域拷贝给 lParam
                    m.Result = new IntPtr(1);
                    result = false;
                    break;
                case Win32API.Messages.WM_MOUSEMOVE:
                    // 如果当前正在拖动滚动条,则更新滚动条位置,并拦截原有事件。
                    if (this.drag_vertical_scroll)
                    {
                        decimal proportion = ((decimal)this.Control.Height - 2) / (this.Control.VerticalScroll.Maximum + 1);
                        int value = drag_scroll_value + (int)((location.Y - drag_scroll_location.Y) / proportion);
                        this.set_vertical_scroll_value(value);
                        result = false;
                    }
                    break;
                case Win32API.Messages.WM_MOUSEWHEEL:
                    arguments = this.Control.VerticalScroll.Value;
                    break;
                case Win32API.Messages.WM_NCMOUSEMOVE:
                    // 如果滚动条之前的状态为焦点,但是鼠标已经移走,则刷新滚动条的UI。
                    if (this.vertical_scroll_foucs && !vertical_scroll_rect.Contains(location))
                        this.InvalidateScroll(m);
                    // 如果滚动条之前的状态无焦点,但是鼠标已经移入,则刷新滚动条的UI。
                    else if (!this.vertical_scroll_foucs && vertical_scroll_rect.Contains(location))
                        this.InvalidateScroll(m);
                    else
                        result = false;
                    break;
                case Win32API.Messages.WM_NCLBUTTONDOWN:
                    // 如果鼠标处于滚动条矩形时按下，则开启拖动模式。
                    if (this.vertical_scroll_rect.Contains(location))
                    {
                        Win32API.User32.SetCapture(m.HWnd);
                        drag_vertical_scroll = true;
                        drag_scroll_value = this.Control.VerticalScroll.Value;
                        drag_scroll_location = location;
                    }
                    // 如果鼠标处于滚动条区域，则根据鼠标点击的位置决定上移或下移一格。
                    else if (this.vertical_scroll_Rectangle.Contains(location))
                    {
                        if (location.Y > vertical_scroll_rect.Bottom)
                            this.set_vertical_scroll_value(this.Control.VerticalScroll.Value + this.Control.Height);
                        else if (location.Y < vertical_scroll_rect.Top)
                            this.set_vertical_scroll_value(this.Control.VerticalScroll.Value - this.Control.Height);
                    }
                    else
                        result = false;
                    break;
                case Win32API.Messages.WM_NCLBUTTONUP:
                    // 关闭拖动模式,并释放鼠标捕捉事件。
                    drag_vertical_scroll = false;
                    Win32API.User32.ReleaseCapture();
                    break;
                case Win32API.Messages.WM_CAPTURECHANGED:
                    // 关闭拖动模式
                    drag_vertical_scroll = false;
                    this.InvalidateScroll(m);
                    break;
            }
            return result;
        }

        /// <summary>
        /// 处理 Windows 消息之后执行。
        /// </summary>
        /// <param name="m"></param>
        /// <param name="arguments"></param>
        public void AfterWndProc(Message m, object arguments)
        {
            bool visibleScroll = this.Control.VerticalScroll.Maximum > this.ClipRectangle.Height || this.Control.AutoScrollMinSize.Width > this.ClipRectangle.Width; //获取是否需要显示滚动条

            switch (m.Msg)
            {
                case 674:
                    // 未找到 674 的说明，但是需要鼠标离开时，刷新滚动条
                    this.InvalidateScroll(m);
                    break;
                case Win32API.Messages.WM_VSCROLL:
                    this.InvalidateScroll(m);
                    break;
                case Win32API.Messages.WM_MOUSEWHEEL:
                    int value = (int)arguments;
                    //if (this.Control.VerticalScroll.Value != value)
                    this.InvalidateScroll(m);
                    break;
                case Win32API.Messages.WM_PAINT:
                    this.InvalidateScroll(m);
                    break;
                case Win32API.Messages.WM_NCPAINT:
                    this.InvalidateScroll(m);
                    break;
                case Win32API.Messages.WM_MOUSELEAVE:
                    this.InvalidateScroll(m);
                    break;
            }
        }

        /// <summary>
        /// 设置垂直滚动条的值。
        /// </summary>
        /// <param name="value"></param>
        internal protected void set_vertical_scroll_value(int value)
        {
            value = Math.Max(0, value);
            value = Math.Min(value, Math.Max(0, this.Control.VerticalScroll.Maximum + 3 - this.Control.Height));
            if (this.Control.VerticalScroll.Value != value)
            {
                Scrollinfo scrollInfo = this.GetScrollInfo();
                SetControlScrollLocation(scrollInfo, value);
            }
        }

        /// <summary>
        /// 获取滚动条消息
        /// </summary>
        /// <returns></returns>
        private Scrollinfo GetScrollInfo()
        {
            Scrollinfo si = new Scrollinfo();
            si.cbSize = (uint)Marshal.SizeOf(si);
            si.fMask = (int)(ScrollInfoMask.SIF_DISABLENOSCROLL | ScrollInfoMask.SIF_ALL);
            Win32API.User32.GetScrollInfo(this.Control.Handle, (int)ScrollBarDirection.SB_VERT, ref si);
            return si;
        }

        /// <summary>
        /// 设置控件的滚动条的位置
        /// </summary>
        /// <param name="scrollInfo"></param>
        /// <param name="pos"></param>
        private void SetControlScrollLocation(Scrollinfo scrollInfo, int pos)
        {
            Scrollinfo info = scrollInfo;
            info.nPos = pos;
            Win32API.User32.SetScrollInfo(this.Control.Handle, (int)ScrollBarDirection.SB_VERT, ref info, true);
            int value = Win32API.Messages.SB_THUMBTRACK | (info.nPos << 16);
            IntPtr wParam = new IntPtr(value);
            Win32API.User32.SendMessage(this.Control.Handle, Win32API.Messages.WM_VSCROLL, wParam, new IntPtr());
        }

        /// <summary>
        /// 刷新滚动条区域
        /// </summary>
        /// <param name="m"></param>
        private void InvalidateScroll(Message m)
        {
            this.InvalidateScroll(m.HWnd);
        }

        /// <summary>
        /// 刷新滚动条区域
        /// </summary>
        /// <param name="hwnd"></param>
        private void InvalidateScroll(IntPtr hwnd)
        {
            if (this.Control.Width == 0 || this.Control.Height == 0)
                return;

            using var bitmap = new Bitmap(this.Control.Width, this.Control.Height);
            Graphics graphics = Graphics.FromImage(bitmap);
            this.InvalidateScroll(graphics);
            if (this.BorderStyle != BorderStyle.None)
            {
                using Pen pen = new(this.BorderColor);
                graphics.DrawRectangle(pen, new Rectangle(0, 0, this.Control.Width - 1, this.Control.Height - 1));
            }
            graphics.Dispose();
            IntPtr hdc = Win32API.User32.GetWindowDC(hwnd);
            graphics = Graphics.FromHdc(hdc);
            graphics.DrawImage(bitmap, 0, 0);
            Win32API.User32.ReleaseDC(hwnd.ToInt32(), hdc.ToInt32());
            graphics.Dispose();
        }

        private void InvalidateScroll(Graphics graphics)
        {
            if (!this.Control.VerticalScroll.Visible)
                return;

            decimal proportion = ((decimal)this.Control.Height - 2) / (this.Control.VerticalScroll.Maximum + 1);
            int height = Convert.ToInt32(Math.Ceiling((this.Control.Height - 2) * proportion)) - 1;
            height = Math.Max(this.scrollSize.Width, height);

            using var bitmap = new Bitmap(this.scrollBorderSize.Width, this.Control.Height);
            using var g = Graphics.FromImage(bitmap);

            int top = (int)(this.Control.VerticalScroll.Value * proportion) + 1;
            int left = (bitmap.Width - scrollSize.Width) / 2;
            var rect = new Rectangle(left, top, scrollSize.Width, height);
            using var path = rect.GetRoundPath();
            vertical_scroll_Rectangle = new Rectangle(this.Control.Width - bitmap.Width, 0, bitmap.Width, this.Control.Height);
            vertical_scroll_rect = new Rectangle(this.Control.Width - bitmap.Width + left, top, scrollSize.Width, height);
            Brush brush = DefaultBrush;
            Point position = this.Control.PointToClient(System.Windows.Forms.Control.MousePosition);
            if (vertical_scroll_rect.Contains(position) || drag_vertical_scroll)
                brush = HoverBrush;
            vertical_scroll_foucs = vertical_scroll_rect.Contains(position);
            if (!this.VirtualScroll)
            {
                this.OnPaintScrollBackgroundImage(new PaintEventArgs(g, new Rectangle(0, 0, bitmap.Width, bitmap.Height)));
            }
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.FillPath(brush, path);
            using Pen pen = new(brush);
            g.DrawPath(pen, path);

            var cipRectangle = this.ClipRectangle;
            left = this.Control.Width - bitmap.Width;
            if (this.BorderStyle != BorderStyle.None)
                left--;
            graphics.DrawImage(bitmap, left, cipRectangle.Top);
        }

        /// <summary>
        /// 绘制滚动条的背景图
        /// </summary>
        /// <param name="e"></param>
        protected void OnPaintScrollBackgroundImage(PaintEventArgs e)
        {
            if (this.PaintScrollBackgroundImage != null)
            {
                this.PaintScrollBackgroundImage.Invoke(this, e);
            }
            else
            {
                Color backColor = this.Control.BackColor;
                if (backColor == Color.Transparent)
                    backColor = System.Drawing.SystemColors.Control;
                using var solidBrush = new SolidBrush(backColor);
                e.Graphics.FillRectangle(solidBrush, e.ClipRectangle);
            }
        }
    }
}
